﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Xml.Linq;

namespace DynamicGeometry
{
    public static class Tuple
    {
        public static Tuple<TP1, TP2> Create<TP1, TP2>(TP1 p1, TP2 p2)
            where TP1 : IEquatable<TP1>
            where TP2 : IEquatable<TP2>
        {
            return new Tuple<TP1, TP2>(p1, p2);
        }
        
        public static Tuple<TP1, TP2, TP3> Create<TP1, TP2, TP3>(TP1 p1, TP2 p2, TP3 p3)
        {
            return new Tuple<TP1, TP2, TP3>(p1, p2, p3);
        }
    }

    public struct Tuple<T1, T2> : IEquatable<Tuple<T1, T2>>
        where T1 : IEquatable<T1>
        where T2 : IEquatable<T2>
    {
        public Tuple(T1 item1, T2 item2) : this()
        {
            Item1 = item1;
            Item2 = item2;
        }

        public T1 Item1 { get; private set; }
        public T2 Item2 { get; private set; }

        public bool Equals(Tuple<T1, T2> other)
        {
            return Item1.Equals(other.Item1) && Item2.Equals(other.Item2);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Tuple<T1, T2>))
            {
                return false;
            }
            return Equals((Tuple<T1, T2>)obj);
        }

        public override int GetHashCode()
        {
            return Item1.GetHashCode() ^ Item2.GetHashCode();
        }

        public override string ToString()
        {
            return "(" + Item1.ToString() + "," + Item2.ToString() + ")";
        }
    }

    public struct Tuple<T1, T2, T3> : IEquatable<Tuple<T1, T2, T3>>
    {
        public Tuple(T1 item1, T2 item2, T3 item3)
            : this()
        {
            Item1 = item1;
            Item2 = item2;
            Item3 = item3;
        }

        public T1 Item1 { get; private set; }
        public T2 Item2 { get; private set; }
        public T3 Item3 { get; private set; }

        public bool Equals(Tuple<T1, T2, T3> other)
        {
            return Item1.Equals(other.Item1) 
                && Item2.Equals(other.Item2)
                && Item3.Equals(other.Item3);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Tuple<T1, T2, T3>))
            {
                return false;
            }
            return Equals((Tuple<T1, T2, T3>)obj);
        }

        public override int GetHashCode()
        {
            return Item1.GetHashCode() 
                ^ Item2.GetHashCode()
                ^ Item3.GetHashCode();
        }

        public override string ToString()
        {
            return "(" + Item1.ToString() + "," + Item2.ToString() + "," + Item3.ToString() + ")";
        }
    }

    public static class Extensions
    {
        public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
        {
            items.ForEach(i => collection.Add(i));
        }

        public static PointPair GetSegment(this IList<Point> points, int startIndex)
        {
            return new PointPair(points[startIndex], points[(startIndex + 1) % points.Count]);
        }

        public static PointPair GetPreviousSegment(this IList<Point> points, int startIndex)
        {
            return new PointPair(points[startIndex > 0 ? startIndex - 1 : points.Count - 1], points[startIndex]);
        }

        public static int RotateNext(this int index, int count)
        {
            return (index + 1) % count;
        }

        public static int RotatePrevious(this int index, int count)
        {
            return index > 0 ? index - 1 : count - 1;
        }

        public static int RotatePrevious(this int index, int count, int steps)
        {
            int result = index - steps;
            if (result < 0)
            {
                result += count;
            }
            return result;
        }

        public static void RemoveLast<T>(this IList<T> list)
        {
            list.RemoveAt(list.Count - 1);
        }

        public static IEnumerable<int> Rotate(this int startIndex, int count)
        {
            while (true)
            {
                startIndex = startIndex.RotateNext(count);
                yield return startIndex;
            }
        }

        //public static void SetZIndex<TShape>(this ShapeBase<TShape> shape, ZOrder typeOfLayer)
        //    where TShape : Shape
        //{
        //    shape.ZIndex = (int)typeOfLayer;
        //    Canvas.SetZIndex(shape.Shape, (int)typeOfLayer);
        //}

        public static void MoveTo(this FrameworkElement element, Point position)
        {
            MoveTo(element, position.X, position.Y);
        }

        public static void CenterAt(this FrameworkElement element, Point center)
        {
            CenterAt(element, center.X, center.Y);
        }

        public static void CenterAt(this FrameworkElement element, double x, double y)
        {
            MoveTo(element, x - element.Width / 2, y - element.Height / 2);
        }

        public static void MoveTo(this FrameworkElement element, double x, double y)
        {
            Canvas.SetLeft(element, x);
            Canvas.SetTop(element, y); 
        }

        public static void MoveOffset(this FrameworkElement element, double xOffset, double yOffset)
        {
            if (element == null || double.IsNaN(xOffset) || double.IsNaN(yOffset))
            {
                return;
            }
            var coordinates = element.GetCoordinates();
            Canvas.SetLeft(element, coordinates.X + xOffset);
            Canvas.SetTop(element, coordinates.Y + yOffset);
        }

        //public static void Move(
        //    this Line line, 
        //    double x1, 
        //    double y1, 
        //    double x2, 
        //    double y2,
        //    CoordinateSystem coordinateSystem)
        //{
        //    var p1 = coordinateSystem.ToPhysical(new Point(x1, y1));
        //    var p2 = coordinateSystem.ToPhysical(new Point(x2, y2));
        //    line.X1 = p1.X;
        //    line.Y1 = p1.Y;
        //    line.X2 = p2.X;
        //    line.Y2 = p2.Y;
        //}

        public static Point GetCoordinates(this FrameworkElement element)
        {
            return new Point(Canvas.GetLeft(element), Canvas.GetTop(element));
        }

        public static IEnumerable<T> AsEnumerable<T>(this T singleElement)
        {
            yield return singleElement;
        }

        public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, T oldItem, T newItem)
            where T : IEquatable<T>
        {
            foreach (var item in source)
            {
                if (item.Equals(oldItem))
                {
                    yield return newItem;
                }
                else
                {
                    yield return item;
                }
            }
        }

        public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
        {
            foreach (var item in items)
            {
                action(item);
            }
        }

        //public static IEnumerable<Point> ToPoints(this IEnumerable<IFigure> figures)
        //{
        //    return figures
        //        .OfType<IPoint>()
        //        .Select(p => p.Coordinates);
        //}

        public static PointCollection ToPointCollection(this IEnumerable<Point> points)
        {
            var result = new PointCollection();
            points.ForEach(result.Add);
            return result;
        }

        //public static IEnumerable<Point> ToLogical(this PointCollection points, CoordinateSystem coordinateSystem)
        //{
        //    return coordinateSystem.ToLogical(points);
        //}

        public static Point SetX(this Point p, double x)
        {
            return new Point(x, p.Y);
        }

        public static Point SetY(this Point p, double y)
        {
            return new Point(p.X, y);
        }

        public static Color ToColor(this string s)
        {
            if (s.IsEmpty())
            {
                return Colors.Black;
            }
            if (s.Length == 7)
            {
                return Color.FromArgb(
                    255,
                    Convert.ToByte(s.Substring(1, 2), 16),
                    Convert.ToByte(s.Substring(3, 2), 16),
                    Convert.ToByte(s.Substring(5, 2), 16));
            }
            if (s.Length == 6)
            {
                return Color.FromArgb(
                    255,
                    Convert.ToByte(s.Substring(0, 2), 16),
                    Convert.ToByte(s.Substring(2, 2), 16),
                    Convert.ToByte(s.Substring(4, 2), 16));
            }
            if (s.Length == 8)
            {
                return Color.FromArgb(
                    Convert.ToByte(s.Substring(0, 2), 16),
                    Convert.ToByte(s.Substring(2, 2), 16),
                    Convert.ToByte(s.Substring(4, 2), 16),
                    Convert.ToByte(s.Substring(6, 2), 16));
            }
            if (s.Length == 9)
            {
                return Color.FromArgb(
                    Convert.ToByte(s.Substring(1, 2), 16),
                    Convert.ToByte(s.Substring(3, 2), 16),
                    Convert.ToByte(s.Substring(5, 2), 16),
                    Convert.ToByte(s.Substring(7, 2), 16));
            }
            return Colors.Black;
        }

        public static bool IsEmpty<T>(this IEnumerable<T> collection)
        {
            return collection == null || !collection.Any();
        }

        public static bool IsEmpty<T>(this IList<T> collection)
        {
            return collection == null || collection.Count == 0;
        }

        public static bool IsEmpty(this string s)
        {
            return string.IsNullOrEmpty(s);
        }

        public static bool HasAttribute<T>(
            this MemberInfo attributeHost)
            where T : Attribute
        {
            return attributeHost.GetAttribute<T>() != null;
        }

        public static T GetAttribute<T>(
            this MemberInfo attributeHost)
            where T : Attribute
        {
            return (T)Attribute.GetCustomAttribute(attributeHost, typeof(T));
        }

        public static bool HasInterface<T>(this Type type)
        {
            return typeof(T).IsAssignableFrom(type);
        }

        public static Visibility ToVisibility(this bool visible)
        {
            return visible ? Visibility.Visible : Visibility.Collapsed;
        }

        /// <summary>
        /// Returns physical coordinates
        /// </summary>
        /// <param name="canvas"></param>
        /// <returns></returns>
        public static PointPair GetBorderRectangle(this FrameworkElement canvas)
        {
            return new PointPair()
            {
                P1 = { X = 0, Y = 0 },
                P2 = { X = canvas.ActualWidth, Y = canvas.ActualHeight }
            };
        }

        public static void Set(this System.Windows.Shapes.Shape shape, Point coordinates)
        {

        }

        public static void Set(this System.Windows.Shapes.Line line, PointPair coordinates)
        {
            line.X1 = coordinates.P1.X;
            line.Y1 = coordinates.P1.Y;
            line.X2 = coordinates.P2.X;
            line.Y2 = coordinates.P2.Y;
        }

        public static string ToStringInvariant(this double number)
        {
            return number.ToString(CultureInfo.InvariantCulture);
        }

        public static double ReadDouble(this XElement element, string attributeName)
        {
            double result = 0;
            var attribute = element.Attribute(attributeName);
            if (attribute != null)
            {
                double.TryParse(
                    attribute.Value,
                    NumberStyles.Float,
                    CultureInfo.InvariantCulture,
                    out result);
            }
            return result;
        }

        public static string ReadString(this XElement element, string attributeName)
        {
            var attribute = element.Attribute(attributeName);
            if (attribute != null)
            {
                return attribute.Value;
            }
            return null;
        }
    }

    public static class Check
    {
        [DebuggerStepThrough]
        public static void NotNull<T>(T obj) where T : class
        {
            if (obj == null)
            {
                throw new ArgumentNullException(typeof(T).FullName);
            }
        }

        [DebuggerStepThrough]
        public static void Positive(double value)
        {
            if (value <= 0)
            {
                throw new ArgumentException("Value should be positive, and it is: " + value.ToString());
            }
        }
    }
}
